pub mod action;
pub mod parse;
pub mod word;
pub mod var;
pub mod error;

pub use action::*;
pub use parse::*;
pub use word::*;
pub use var::*;
pub use error::*;
use log::LevelFilter;
use superfeature::destor_acquire;

use std::path::PathBuf;
use crate::error::{CResult, ChunkIoError};
//use error::ConfigError;
use crate::mylog::{mylog_init, config_log_level};
use crate::recipe::VersionControlBlock;
use crate::backup::BackupConfig;
use crate::restore::restore;
use crate::superfeature::super_feature_algorithm_acquire;
use crate::constants::{DefaultChunkPointer, VERSION_DIR_NAME};

//default config，it just can be set var
pub const DEFAULT_CONFIG_STR:&str = r#"
    chunking algorithm = rabin
    super feature algorithm = superfeature
    working directory = ./test/backup
    log level = no
    chunk max size = 65536
    chunk min size = 2048
    chunk avg size = 8192
    super feature subregion num = 12
    features per sf = 4
    enable sort features = false
    find_in_recent_buffer = true
    find_in_read_cache = true
    find_in_write_buf = true
    find_in_index_store = true
"#;

pub struct Config {
    working_directory:Option<String>,
    log_level:Option<LevelFilter>,
    sf_num:usize,
    output_option:bool,
    prefix_output_option:bool,
    time_output_option:bool,
    find_in_recent_buffer:bool,
    find_in_read_cache:bool,
    find_in_write_buf:bool,
    find_in_index_store:bool,
}

impl Config {
    pub fn new() -> Config {
        Config {
            working_directory: None, 
            log_level:None,
            sf_num:3,
            output_option:false,
            prefix_output_option:false,
            time_output_option:false,
            find_in_recent_buffer:false,
            find_in_read_cache:false,
            find_in_write_buf:false,
            find_in_index_store:false,
        }
    }

    pub fn output(mut self,output_opt:bool) -> Self {
        self.output_option = output_opt;
        self
    }

    pub fn time_output(mut self,output_opt:bool) -> Self {
        self.time_output_option = output_opt;
        self
    }

    pub fn prefix_output(mut self,output_opt:bool) -> Self {
        self.prefix_output_option = output_opt;
        self
    }

    pub fn run(path:&str) -> Self {
        let config_string = std::fs::read_to_string(path).unwrap_or("".to_string());
        Config::default()
            .output(true)
            .extend(&config_string)
            .map_err(|(e,statement)|{
                println!("run {} error at {:?}",path,statement);
                e
            })
            .expect("")
    }

    pub fn set_var(&mut self,var_name:&str,value:&str) -> ConfigResult<()> {
        if match_word_format(var_name, "super feature algorithm")       { return self.set_superfeature_algorithm(value); }
        if match_word_format(var_name, "super feature method")          { return self.set_superfeature_algorithm(value); }
        if match_word_format(var_name, "working directory")             { return self.set_working_directory(value); }
        if match_word_format(var_name, "log level")                     { return self.set_log_level(value); }
        if match_word_format(var_name, "sf num")                        { return self.set_sf_num(value); }
        if match_word_format(var_name, "chunk max size")                { return self.set_chunk_max_size(value); }
        if match_word_format(var_name, "chunk min size")                { return self.set_chunk_min_size(value); }
        if match_word_format(var_name, "chunk avg size")                { return self.set_chunk_avg_size(value); }
        if match_word_format(var_name, "super feature subregion num")   { return self.set_super_feature_subregion_num(value); }
        if match_word_format(var_name, "features per sf")               { return self.set_features_per_sf(value); }
        if match_word_format(var_name, "enable sort features")          { return self.set_enable_sort_features(value); }
        if match_word_format(var_name, "chunking algorithm")            { return self.set_chunking_algorithm(value); }
        if match_word_format(var_name, "chunking method")               { return self.set_chunking_algorithm(value); }
        if match_word_format(var_name, "find in recent buffer")         { return self.set_find_in_recent_buffer(value); }
        if match_word_format(var_name, "find in write buf")             { return self.set_find_in_write_buf(value); }
        if match_word_format(var_name, "find in read cache")            { return self.set_find_in_read_cache(value); }
        if match_word_format(var_name, "find in index store")           { return self.set_find_in_index_store(value); }
        Err(ConfigError::InvalidSetVar(var_name.to_string()))
    }

    pub fn get_var(&self,var_name:&str) -> ConfigResult<String> {
        if match_word_format(var_name, "super feature algorithm")       { return self.get_superfeature_algorithm(); }
        if match_word_format(var_name, "working directory")             { return self.get_working_directory(); }
        if match_word_format(var_name, "log level")                     { return self.get_log_level(); }
        if match_word_format(var_name, "sf num")                        { return self.get_sf_num(); }
        if match_word_format(var_name, "chunk max size")                { return self.get_chunk_max_size(); }
        if match_word_format(var_name, "chunk min size")                { return self.get_chunk_min_size(); }
        if match_word_format(var_name, "chunk avg size")                { return self.get_chunk_avg_size(); }
        if match_word_format(var_name, "super feature subregion num")   { return self.get_super_feature_subregion_num(); }
        if match_word_format(var_name, "features per sf")               { return self.get_features_per_sf(); }
        if match_word_format(var_name, "enable sort features")          { return self.get_enable_sort_features(); }
        if match_word_format(var_name, "chunking algorithm")            { return self.get_chunking_algorithm(); }
        if match_word_format(var_name, "find in recent buffer")         { return self.get_find_in_recent_buffer(); }
        if match_word_format(var_name, "find in write buf")             { return self.get_find_in_write_buf(); }
        if match_word_format(var_name, "find in read cache")            { return self.get_find_in_read_cache(); }
        if match_word_format(var_name, "find in index store")           { return self.get_find_in_index_store(); }
        Err(ConfigError::InvalidGetVar(var_name.to_string()))
    }

    pub fn time_display(&self) -> bool {
        self.output_option && self.time_output_option
    }

    pub fn prefix_display(&self) -> bool {
        self.output_option && self.prefix_output_option
    }

    pub fn set_find_in_recent_buffer(&mut self,value:&str) -> ConfigResult<()> {
        let find_in_recent_buffer = value.parse().map_err(
            |o|ConfigError::BoolTypeMismatch("find in recenr buffer".to_string(), o))?;
        self.find_in_recent_buffer = find_in_recent_buffer;
        Ok(())
    }

    pub fn get_find_in_recent_buffer(&self) -> ConfigResult<String> {
        Ok(self.find_in_recent_buffer.to_string())
    }

    pub fn set_find_in_write_buf(&mut self,value:&str) -> ConfigResult<()> {
        let find_in_write_buf = value.parse().map_err(
            |o|ConfigError::BoolTypeMismatch("find in write buf".to_string(), o))?;
        self.find_in_write_buf = find_in_write_buf;
        Ok(())
    }

    pub fn get_find_in_write_buf(&self) -> ConfigResult<String> {
        Ok(self.find_in_write_buf.to_string())
    }

    pub fn set_find_in_read_cache(&mut self,value:&str) -> ConfigResult<()> {
        let find_in_read_cache = value.parse().map_err(
            |o|ConfigError::BoolTypeMismatch("find in read cache".to_string(), o))?;
        self.find_in_read_cache = find_in_read_cache;
        Ok(())
    }

    pub fn get_find_in_read_cache(&self) -> ConfigResult<String> {
        Ok(self.find_in_read_cache.to_string())
    }

    pub fn set_find_in_index_store(&mut self,value:&str) -> ConfigResult<()> {
        let find_in_index_store = value.parse().map_err(
            |o|ConfigError::BoolTypeMismatch("find in index store".to_string(), o))?;
        self.find_in_index_store = find_in_index_store;
        Ok(())
    }

    pub fn get_find_in_index_store(&self) -> ConfigResult<String> {
        Ok(self.find_in_index_store.to_string())
    }

    pub fn set_superfeature_algorithm(&mut self,value:&str) -> ConfigResult<()> {
        crate::superfeature::config_super_feature_algorithm(value)
            .map_err(|op|ConfigError::UnknownSuperfeatureAlgorithm(op))
    }

    pub fn get_superfeature_algorithm(&self) -> ConfigResult<String> {
        Ok(format!("{:?}",crate::superfeature::super_feature_algorithm_acquire()))
    }

    pub fn set_working_directory(&mut self,value:&str) -> ConfigResult<()> {
        self.working_directory = Some(value.to_string());
        Ok(())
    }

    pub fn get_working_directory(&self) -> ConfigResult<String> {
        self.working_directory.as_ref().map_or(
            Err(ConfigError::WoringDirMissing),
            |w|Ok(w.clone())
        )
    }

    pub fn set_log_level(&mut self,value:&str) -> ConfigResult<()> {
        let old_log_level = self.log_level;
        self.log_level = config_log_level(value);
        if let Some(_) = old_log_level {
            if self.log_level.is_none() {
                return Err(ConfigError::LoggerSetNone);
            } 
        } else {
            if self.log_level.is_some() {
                return Ok(mylog_init()?);
            }
        }
        Ok(())
    }

    pub fn get_log_level(&self) -> ConfigResult<String> {
        Ok(format!("{:?}",self.log_level))
    }

    pub fn set_sf_num(&mut self,value:&str) -> ConfigResult<()> {
        let sf_num:usize = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("sf num".to_string(), o))?;
        if sf_num != 3 && sf_num != 4 {  return Err(ConfigError::SfNumOverFlow); }
        self.sf_num = sf_num;
        Ok(())
    }

    pub fn get_sf_num(&self) -> ConfigResult<String> {
        Ok(self.sf_num.to_string())
    }

    pub fn set_chunk_max_size(&mut self,value:&str) -> ConfigResult<()> {
        let chunk_max_size = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("chunk max size".to_string(), o))?;
        destor_acquire().config_chunk_max_size(chunk_max_size);
        Ok(())
    }

    pub fn get_chunk_max_size(&self) -> ConfigResult<String> {
        Ok(destor_acquire().chunk_max_size().to_string())
    }

    pub fn set_chunk_min_size(&mut self,value:&str) -> ConfigResult<()> {
        let chunk_min_size = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("chunk min size".to_string(), o))?;
        destor_acquire().config_chunk_min_size(chunk_min_size);
        Ok(())
    }

    pub fn get_chunk_min_size(&self) -> ConfigResult<String> {
        Ok(destor_acquire().chunk_min_size().to_string())
    }

    pub fn set_chunk_avg_size(&mut self,value:&str) -> ConfigResult<()> {
        let chunk_avg_size = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("chunk avg size".to_string(), o))?;
        destor_acquire().config_chunk_avg_size(chunk_avg_size);
        Ok(())
    }

    pub fn get_chunk_avg_size(&self) -> ConfigResult<String> {
        Ok(destor_acquire().chunk_avg_size().to_string())
    }
    
    pub fn set_super_feature_subregion_num(&mut self,value:&str) -> ConfigResult<()> {
        let super_feature_subregion_num = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("super feature subregion num".to_string(), o))?;
        destor_acquire().config_super_feature_subregion_num(super_feature_subregion_num);
        Ok(())
    }

    pub fn get_super_feature_subregion_num(&self) -> ConfigResult<String> {
        Ok(destor_acquire().super_feature_subregion_num().to_string())
    }
    
    pub fn set_features_per_sf(&mut self,value:&str) -> ConfigResult<()> {
        let features_per_sf = value.parse().map_err(
            |o|ConfigError::IntTypeMismatch("features per sf".to_string(), o))?;
        destor_acquire().config_features_per_sf(features_per_sf);
        Ok(())
    }

    pub fn get_features_per_sf(&self) -> ConfigResult<String> {
        Ok(destor_acquire().feature_per_sf().to_string())
    }
    
    pub fn set_enable_sort_features(&mut self,value:&str) -> ConfigResult<()> {
        let enable_sort_features = value.parse().map_err(
            |o|ConfigError::BoolTypeMismatch("enable sort features".to_string(), o))?;
        destor_acquire().config_enable_sort_features(enable_sort_features);
        Ok(())
    }

    pub fn get_enable_sort_features(&self) -> ConfigResult<String> {
        Ok(destor_acquire().enable_sort_features().to_string())
    }

    pub fn set_chunking_algorithm(&mut self,value:&str) -> ConfigResult<()> {
        crate::backup::read::config_chunking(value)?;
        destor_acquire().config_chunk_algorithm(value);
        Ok(())
    }

    pub fn get_chunking_algorithm(&self) -> ConfigResult<String> {
        Ok(destor_acquire().chunk_algorithm())
    }


    #[inline(always)]
    pub fn extend(mut self,statements:&str) -> Result<Self,(ChunkIoError,Statement)> {
        let mut raw_str = statements;
        let mut statement_no = 1;
        while raw_str!= "" {
            let (new_raw_str,statement) = match Statement::parse(raw_str) {
                Ok(compile_result) => compile_result,
                Err(e) => {
                    println!("statement {} compile error:{}",statement_no,e);
                    break;
                }
            };
            if !statement.is_notes() {
                let result = self.execute(&statement);
                if self.output_option {
                    match result {
                        Ok(s) => { 
                            if self.prefix_display() {
                                println!("command {} execute ok:{}",statement_no,s); 
                            } else {
                                println!("{}",s)
                            }
                        }
                        Err(e) => { 
                            if self.prefix_display() {
                                println!("command {} execute error",statement_no); 
                            }
                            return Err((e,statement));
                        }
                    }                
                }
                statement_no += 1;
            }
            raw_str = new_raw_str;
        }
        Ok(self)
    }

    fn ready(&self) -> CResult<()> {
        destor_acquire().chunk_algorithm_init();
        Ok(())
    }

}

impl<'a> Execute<VarStatement<'a>> for Config {
    fn execute(&mut self,t:&VarStatement) -> CResult<String> {
        if t.value() == "?" {
            Ok(format!("{} = {}",t.var(),self.get_var(t.var())?))
        } else {
            self.set_var(t.var(), t.value())?;
            Ok("success".to_string())
        }
    }
}

impl<'a> Execute<BackupStatement<'a>> for Config {
    fn execute(&mut self,t:&BackupStatement) -> CResult<String> {
        self.ready()?;
        let path = t.path()?;
        let default_working_directory = self.get_working_directory()?;
        let working_directory = t.working_directory().unwrap_or(default_working_directory.as_str());
        let version = t.version();
        let display_version = if version == "" { "default" } else { version };
        let local_output = t.output();
        let time_output = self.time_display() || local_output.is_some();
        if !super_feature_algorithm_acquire().is_smooth() && self.sf_num == 4 {
            return Err(ChunkIoError::Config(ConfigError::NoneSmoothSfNumOverFlow));
        }
        let backup_config = BackupConfig {
            sf_num:self.sf_num,
            find_in_recent_buffer:self.find_in_recent_buffer,
            find_in_read_cache:self.find_in_read_cache,
            find_in_write_buf:self.find_in_write_buf,
            find_in_index_store:self.find_in_index_store,
        };
        let statistics = backup_config
            .backup::<DefaultChunkPointer>(path,working_directory,version)?;
        if time_output {
            Ok(format!("backup {} to {} at version {}\n{}"
                ,path,working_directory
                ,display_version
                ,statistics
                    .lock()
                    .to_result(local_output)
            ))
        } else {
            Ok(format!("backup {} to {} at version {}",path,working_directory,display_version))
        }
    }
}

impl<'a> Execute<RestoreStatement<'a>> for Config {
    fn execute(&mut self,t:&RestoreStatement) -> CResult<String> {
        let path = t.path()?;
        let default_working_directory = self.get_working_directory()?;
        let working_directory = t.working_directory().unwrap_or(default_working_directory.as_str());
        let version = t.version();
        let local_output = t.output();
        let time_output = self.time_display() || local_output.is_some();
        let display_version = if let None = version { "default".to_string() } else { format!("{:?}",version) };
        let statistics = restore::<DefaultChunkPointer,_>(working_directory,path,version)?;
        if time_output {
            Ok(format!("restore {} from {} at version {}\n{}"
                    ,path,working_directory
                    ,display_version,
                    statistics
                        .lock()
                        .to_result(local_output)
            ))
        } else {
            Ok(format!("restore {} from {} at version {}",path,working_directory,display_version))
        }
    }
}

impl<'a> Execute<VersionStatement<'a>> for Config {
    fn execute(&mut self,t:&VersionStatement) -> CResult<String> {
        let opt_arg = t.opt_arg();
        let default_working_directory = self.get_working_directory()?;
        let working_directory = t.working_directory().unwrap_or(default_working_directory.as_str());
        let working_path =  PathBuf::from(working_directory);
        let vcb = VersionControlBlock::from_path(&working_path.clone().join(VERSION_DIR_NAME))?;
        match opt_arg {
            VersionStatementArg::All => {
                Ok(vcb.get_all_version()?.map_or(
                    format!("no version in working directory {}",working_directory), 
                    |version_info|format!("version info in {} as follow\n{}",working_directory,version_info)
                ))
            },
            VersionStatementArg::Find(version) => {
                Ok(vcb.seek_version(version.clone())?.map_or(
                    format!("version {:?} in {} doesn't exist",version,working_directory),
                    |result|format!("version {:?} in {} is at {}",version,working_directory,result)
                ))
            },
        }
    }
}

impl<'a> Execute<Help<'a>> for Config {
    fn execute(&mut self,t:&Help) -> CResult<String> {
        Ok(t.execute())
    }
}

impl<'a> Execute<Statement<'a>> for Config {
    fn execute(&mut self,t:&Statement) -> CResult<String> {
        match t {
            Statement::Var(assign) => self.execute(assign),
            Statement::Backup(backup) => self.execute(backup),
            Statement::Restore(restore) => self.execute(restore),
            Statement::Version(version) => self.execute(version),
            Statement::Help(help) => self.execute(help),
            Statement::Notes => Ok("just a note".to_string()),
        }
    }
}

impl Default for Config {
    fn default() -> Self {
        Config::new().extend(DEFAULT_CONFIG_STR).expect("default config error")
    }
}

